home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1983, 1988 The Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley. The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- */
-
- #ifndef lint
- char copyright[] =
- "@(#) Copyright (c) 1983, 1988 The Regents of the University of California.\n\
- All rights reserved.\n";
- #endif /* not lint */
-
- #ifndef lint
- static char sccsid[] = "@(#)rshd.c 5.17.1.2 (Berkeley) 2/7/89";
- #endif /* not lint */
-
- /*
- * Remote shell server. We're invoked by the rcmd(3) function.
- */
-
- #include <sys/param.h>
- #include <sys/ioctl.h>
- #include <sys/socket.h>
- #include <sys/file.h>
- #include <sys/time.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <stdio.h>
- #include <varargs.h>
- #include <pwd.h>
- #include <signal.h>
- #include <netdb.h>
- #include <syslog.h>
- #include <errno.h>
- extern int errno;
-
- char *index();
- char *rindex();
- char *strncat();
-
- int keepalive = 1; /* flag for SO_KEEPALIVE socket option */
- int one = 1; /* used for setsockopt() and ioctl() */
-
- char env_user[20] = "USER="; /* the environment strings we set */
- char env_home[64] = "HOME=";
- char env_shell[64] = "SHELL=";
- char *env_ptrs[] =
- {env_home, env_shell, "PATH=/usr/ucb:/bin:/usr/bin:", env_user, 0};
- char **environ;
-
- /*ARGSUSED*/
- main(argc, argv)
- int argc;
- char **argv;
- {
- int ch, addrlen;
- struct sockaddr_in cli_addr;
- struct linger linger;
- extern int opterr, optind; /* in getopt() */
- extern int _check_rhosts_file; /* in validuser() */
-
- openlog("rsh", LOG_PID | LOG_ODELAY, LOG_DAEMON);
-
- opterr = 0;
- while ( (ch = getopt(argc, argv, "ln")) != EOF)
- switch((char) ch) {
- case 'l':
- _check_rhosts_file = 0; /* don't check .rhosts file */
- break;
-
- case 'n':
- keepalive = 0; /* don't enable SO_KEEPALIVE */
- break;
-
- case '?':
- default:
- syslog(LOG_ERR, "usage: rshd [-l]");
- break;
- }
-
- argc -= optind;
- argv += optind;
-
- /*
- * We assume we're invoked by inetd, so the socket that the connection
- * is on, is open on descriptors 0, 1 and 2.
- *
- * First get the Internet address of the client process.
- * This is required for all the authentication we perform.
- */
-
- addrlen = sizeof(cli_addr);
- if (getpeername(0, (struct sockaddr *) &cli_addr, &addrlen) < 0) {
- fprintf(stderr, "%s: ", argv[0]);
- perror("getpeername");
- _exit(1);
- }
-
- /*
- * Set the socket options: SO_KEEPALIVE and SO_LINGER.
- */
-
- if (keepalive &&
- setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *) &one,
- sizeof(one)) < 0)
- syslog(LOG_WARNING, "setsockopt(SO_KEEPALIVE): %m");
-
- linger.l_onoff = 1;
- linger.l_linger = 60;
- if (setsockopt(0, SOL_SOCKET, SO_LINGER, (char *) &linger,
- sizeof(linger)) < 0)
- syslog(LOG_WARNING, "setsockopt(SO_LINGER): %m");
-
- doit(&cli_addr);
- /* doit() never returns */
- }
-
- doit(cli_addrp)
- struct sockaddr_in *cli_addrp; /* client's Internet address */
- {
- int sockfd2, pipefd[2], childpid,
- maxfdp1, cc, oursecport;
- fd_set ready, readfrom;
- short clisecport;
- char *cp, *hostname;
- char servuname[16], cliuname[16], cmdbuf[NCARGS+1];
- char remotehost[2 * MAXHOSTNAMELEN + 1];
- char buf[BUFSIZ], c, sigval;
- struct passwd *pwd;
- struct hostent *hp;
-
- signal(SIGINT, SIG_DFL);
- signal(SIGQUIT, SIG_DFL);
- signal(SIGTERM, SIG_DFL);
-
- #ifdef DEBUG
- {
- int t = open("/dev/tty", 2);
- if (t >= 0) {
- ioctl(t, TIOCNOTTY, (char *) 0);
- close(t);
- }
- }
- #endif
-
- /*
- * Verify that the client's address is an Internet address.
- */
-
- if (cli_addrp->sin_family != AF_INET) {
- syslog(LOG_ERR, "malformed from address\n");
- exit(1);
- }
-
- #ifdef IP_OPTIONS
- {
- u_char optbuf[BUFSIZ/3], *optptr;
- char lbuf[BUFSIZ], *lptr;
- int optsize, ipproto;
- struct protoent *ip;
-
- if ( (ip = getprotobyname("ip")) != NULL)
- ipproto = ip->p_proto;
- else
- ipproto = IPPROTO_IP;
-
- optsize = sizeof(optbuf);
- if (getsockopt(0, ipproto, IP_OPTIONS, (char *) optbuf, &optsize) == 0
- && optsize != 0) {
- /*
- * The client has set IP options. This isn't allowed.
- * Use syslog() to record the fact.
- */
-
- lptr = lbuf;
- optptr = optbuf;
- for ( ; optsize > 0; optptr++, optsize--, lptr += 3)
- sprintf(lptr, " %2.2x", *optptr);
- /* print each option byte as 3 ASCII chars */
- syslog(LOG_NOTICE,
- "Connection received using IP options (ignored): %s", lbuf);
-
- /*
- * Turn off the options. If this doesn't work, we quit.
- */
-
- if (setsockopt(0, ipproto, IP_OPTIONS,
- (char *) NULL, &optsize) != 0) {
- syslog(LOG_ERR, "setsockopt IP_OPTIONS NULL: %m");
- exit(1);
- }
- }
- }
- #endif
-
- /*
- * Verify that the client's address was bound to a reserved port.
- */
-
- cli_addrp->sin_port = ntohs((u_short) cli_addrp->sin_port);
- /* need host byte ordered port# to compare */
- if (cli_addrp->sin_port >= IPPORT_RESERVED ||
- cli_addrp->sin_port < IPPORT_RESERVED/2) {
- syslog(LOG_NOTICE, "Connection from %s on illegal port",
- inet_ntoa(cli_addrp->sin_addr));
- exit(1);
- }
-
- /*
- * Read the ASCII string specifying the secondary port# from
- * the socket. We set a timer of 60 seconds to do this read,
- * else we assume something is wrong. If the client doesn't want
- * the secondary port, they just send the terminating null byte.
- */
-
- alarm(60);
- clisecport = 0;
- for ( ; ; ) {
- if ( (cc = read(0, &c, 1)) != 1) {
- if (cc < 0)
- syslog(LOG_NOTICE, "read: %m");
- shutdown(0, 2);
- exit(1);
- }
- if (c == 0) /* null byte terminates the string */
- break;
- clisecport = (clisecport * 10) + (c - '0');
- }
- alarm(0);
-
- if (clisecport != 0) {
- /*
- * If the secondary port# is nonzero, then we have to
- * connect to that port (which the client has already
- * created and is listening on). The secondary port#
- * that the client tells us to connect to has to also be
- * a reserved port#. Also, our end of this secondary
- * connection has to also have a reserved TCP port bound
- * to it, plus.
- */
-
- if (clisecport >= IPPORT_RESERVED) {
- syslog(LOG_ERR, "2nd port not reserved\n");
- exit(1);
- }
-
- oursecport = IPPORT_RESERVED - 1; /* starting port# to try */
- if ( (sockfd2 = rresvport(&oursecport)) < 0) {
- syslog(LOG_ERR, "can't get stderr port: %m");
- exit(1);
- }
-
- /*
- * Use the cli_addr structure that we already have.
- * The 32-bit Internet address is obviously that of the
- * client's, just change the port# to the one specified
- * by the client as the secondary port.
- */
-
- cli_addrp->sin_port = htons((u_short) clisecport);
- if (connect(sockfd2, (struct sockaddr *) cli_addrp,
- sizeof(*cli_addrp)) < 0) {
- syslog(LOG_INFO, "connect second port: %m");
- exit(1);
- }
- }
-
- /*
- * Get the "name" of the client from its Internet address.
- * This is used for the authentication below.
- */
-
- hp = gethostbyaddr((char *) &cli_addrp->sin_addr,
- sizeof(struct in_addr), cli_addrp->sin_family);
- if (hp) {
- /*
- * If the name returned by gethostbyaddr() is in our domain,
- * attempt to verify that we haven't been fooled by someone
- * in a remote net. Look up the name and check that this
- * address corresponds to the name.
- */
-
- if (local_domain(hp->h_name)) {
- strncpy(remotehost, hp->h_name, sizeof(remotehost) - 1);
- remotehost[sizeof(remotehost) - 1] = 0;
- if ( (hp = gethostbyname(remotehost)) == NULL) {
- syslog(LOG_INFO,
- "Couldn't look up address for %s",
- remotehost);
- my_error("Couldn't look up addr for your host");
- exit(1);
- }
- for ( ; ; hp->h_addr_list++) {
- if (bcmp(hp->h_addr_list[0],
- (caddr_t) &cli_addrp->sin_addr,
- sizeof(cli_addrp->sin_addr)) == 0)
- break; /* equal, OK */
-
- if (hp->h_addr_list[0] == NULL) {
- syslog(LOG_NOTICE,
- "Host addr %s not listed for host %s",
- inet_ntoa(cli_addrp->sin_addr),
- hp->h_name);
- my_error("Host address mismatch");
- exit(1);
- }
- }
- }
- hostname = hp->h_name;
- } else
- hostname = inet_ntoa(cli_addrp->sin_addr);
-
- /*
- * Read three strings from the client.
- */
-
- getstr(cliuname, sizeof(cliuname), "cliuname");
- getstr(servuname, sizeof(servuname), "servuname");
- getstr(cmdbuf, sizeof(cmdbuf), "command");
-
- /*
- * Look up servuname in the password file. The servuname has
- * to be a valid account on this system.
- */
-
- setpwent();
- if ( (pwd = getpwnam(servuname)) == (struct passwd *) NULL) {
- my_error("Login incorrect.\n");
- exit(1);
- }
- endpwent();
-
- /*
- * We'll execute the client's command in the home directory
- * of servuname.
- */
-
- if (chdir(pwd->pw_dir) < 0) {
- chdir("/");
- #ifdef notdef
- my_error("No remote directory.\n");
- exit(1);
- #endif
- }
-
- if (pwd->pw_passwd != NULL && *pwd->pw_passwd != '\0' &&
- ruserok(hostname, pwd->pw_uid == 0, cliuname, servuname) < 0) {
- my_error("Permission denied.\n");
- exit(1);
- }
-
- /*
- * If the servuname isn't root, then check if logins are disabled.
- */
-
- if (pwd->pw_uid != 0 && access("/etc/nologin", F_OK) == 0) {
- my_error("Logins currently disabled.\n");
- exit(1);
- }
-
- /*
- * Now write the null byte back to the client telling it
- * that everything is OK.
- * Note that this means that any error messages that we generate
- * from now on (such as the perror() if the execl() fails), won't
- * be seen by the rcmd() function, but will be seen by the
- * application that called rcmd() when it reads from the socket.
- */
-
- if (write(2, "", 1) != 1)
- exit(1);
-
- if (clisecport) {
- /*
- * We need a secondary channel. Here's where we create
- * the control process that'll handle this secondary
- * channel.
- * First create a pipe to use for communication between
- * the parent and child, then fork.
- */
-
- if (pipe(pipefd) < 0) {
- my_error("Can't make pipe.\n");
- exit(1);
- }
-
- if ( (childpid = fork()) == -1) {
- my_error("Try again.\n");
- exit(1);
- }
-
- if (pipefd[0] > sockfd2) /* set max fd + 1 for select */
- maxfdp1 = pipefd[0];
- else
- maxfdp1 = sockfd2;
- maxfdp1++;
-
- if (childpid != 0) {
- /*
- * Parent process == control process.
- * We: (1) read from the pipe and write to sockfd2;
- * (2) read from sockfd2 and send corresponding
- * signal.
- */
-
- close(0); /* child handles the original socket */
- close(1); /* (0, 1, and 2 were from inetd) */
- close(2);
- close(pipefd[1]); /* close write end of pipe */
-
- FD_ZERO(&readfrom);
- FD_SET(sockfd2, &readfrom);
- FD_SET(pipefd[0], &readfrom);
-
- ioctl(pipefd[0], FIONBIO, (char *) &one);
- /* should set sockfd2 nbio! */
- do {
- ready = readfrom;
- if (select(maxfdp1, &ready, (fd_set *) 0,
- (fd_set *) 0, (struct timeval *) 0) < 0)
- /* wait until something to read */
- break;
-
- if (FD_ISSET(sockfd2, &ready)) {
- if (read(sockfd2, &sigval, 1) <= 0)
- FD_CLR(sockfd2, &readfrom);
- else
- killpg(childpid, sigval);
- }
-
- if (FD_ISSET(pipefd[0], &ready)) {
- errno = 0;
- cc = read(pipefd[0], buf, sizeof(buf));
- if (cc <= 0) {
- shutdown(sockfd2, 2);
- FD_CLR(pipefd[0], &readfrom);
- } else
- write(sockfd2, buf, cc);
- }
- } while (FD_ISSET(sockfd2, &readfrom) ||
- FD_ISSET(pipefd[0], &readfrom));
- /*
- * The pipe will generate an EOF when the shell
- * terminates. The socket will terminate when the
- * client process terminates.
- */
-
- exit(0);
- }
-
- /*
- * Child process. Become a process group leader, so that
- * the control process above can send signals to all the
- * processes we may be the parent of. The process group ID
- * (the getpid() value below) equals the childpid value from
- * the fork above.
- */
-
- setpgrp(0, getpid());
- close(sockfd2); /* control process handles this fd */
- close(pipefd[0]); /* close read end of pipe */
- dup2(pipefd[1], 2); /* stderr of shell has to go through
- pipe to control process */
- close(pipefd[1]);
- }
-
- if (*pwd->pw_shell == '\0')
- pwd->pw_shell = "/bin/sh";
-
- /*
- * Set the gid, then uid to become the user specified by "servuname".
- */
-
- setgid((gid_t) pwd->pw_gid);
- initgroups(pwd->pw_name, pwd->pw_gid); /* BSD groups */
- setuid((uid_t) pwd->pw_uid);
-
- /*
- * Set up an initial environment for the shell that we exec().
- */
-
- environ = env_ptrs;
- strncat(env_home, pwd->pw_dir, sizeof(env_home)-6);
- strncat(env_shell, pwd->pw_shell, sizeof(env_shell)-7);
- strncat(env_user, pwd->pw_name, sizeof(env_user)-6);
-
- if ( (cp = rindex(pwd->pw_shell, '/')) != NULL)
- cp++; /* step past first slash */
- else
- cp = pwd->pw_shell; /* no slash in shell string */
-
- execl(pwd->pw_shell, cp, "-c", cmdbuf, (char *) 0);
-
- perror(pwd->pw_shell); /* error from execl() */
- exit(1);
- }
-
- /*
- * Read a string from the socket. Make sure it fits, else fatal error.
- */
-
- getstr(buf, cnt, errmesg)
- char *buf;
- int cnt; /* sizeof() the char array */
- char *errmesg; /* in case error message required */
- {
- char c;
-
- do {
- if (read(0, &c, 1) != 1)
- exit(1); /* error or EOF */
- *buf++ = c;
- if (--cnt == 0) {
- my_error("%s too long.\n", errmesg);
- exit(1);
- }
- } while (c != 0); /* null byte terminates the string */
- }
-
- /*
- * Send an error message back to the rcmd() client.
- * The first byte we send must be binary 1, followed by the ASCII
- * error message, followed by a newline.
- */
-
- my_error(va_alist)
- va_dcl
- {
- va_list args;
- char *fmt, buff[BUFSIZ];
-
- va_start(args);
- fmt = va_arg(args, char *);
- buff[0] = 1;
- vsprintf(buff + 1, fmt, args);
- va_end(args);
-
- write(2, buff, strlen(buff)); /* fd 2 = socket, from inetd */
- }
-
- /*
- * Check whether the specified host is in our local domain, as determined
- * by the part of the name following the first period, in its name and in ours.
- * If either name is unqualified (contains no period), assume that the host
- * is local, as it will be interpreted as such.
- */
-
- int /* return 1 if local domain, else return 0 */
- local_domain(host)
- char *host;
- {
- register char *ptr1, *ptr2;
- char localhost[MAXHOSTNAMELEN];
-
- if ( (ptr1 = index(host, '.')) == NULL)
- return(1); /* no period in remote host name */
-
- gethostname(localhost, sizeof(localhost));
- if ( (ptr2 = index(localhost, '.')) == NULL)
- return(1); /* no period in local host name */
-
- /*
- * Both host names contain a period. Now compare both names,
- * starting with the first period in each name (i.e., the names
- * of their respective domains). If equal, then the remote domain
- * equals the local domain, return 1.
- */
-
- if (strcasecmp(ptr1, ptr2) == 0) /* case insensitive compare */
- return(1);
-
- return(0);
- }
-